<?php


namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;

trait ModelTrait
{

    private static $instance;

    private static $rules;

    public static function Instance()
    {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function create($attributes)
    {
        return parent::create($attributes);
    }

    protected function serializeDate(\DateTimeInterface $date)
    {
        return $date->format('Y-m-d H:i:s');
    }

    public function countByField($field = '', $where = [])
    {
        $query = self::query();
        if ($where) {
            $query = $query->where($where);
        }

        if ($field && in_array($field, $this->columns)) {
            $return = [

            ];

            if ($field == 'is_active') {
                $return['is_active'] = [
                    'is_active-0' => 0,
                    'is_active-1' => 0,
                ];
            }

            $array = $query->selectRaw("$field,count(*) as `count`")->groupBy($field)->get();
            //return $array;
            if ($array) {
                foreach ($array as $value) {
                    $return[$field]["$field-{$value[$field]}"] = $value['count'];
                }
            }
            return $return;
        }
        return $query->selectRaw("count(*) as `count`")->first()['count'];
    }

    public static function columns($with = 1): array
    {
        $columns = self::Instance()->columns;
        if ($with & Model::WITH_CREATE) {
            in_array('created_at', self::Instance()->fillable) && $columns[] = 'created_at';
            in_array('created_by', self::Instance()->fillable) && $columns[] = 'created_by';
        }
        if ($with & Model::WITH_UPDATE) {
            in_array('updated_at', self::Instance()->fillable) && $columns[] = 'updated_at';
            in_array('updated_by', self::Instance()->fillable) && $columns[] = 'updated_by';
        }
        if ($with & Model::WITH_DELETE) {
            in_array('deleted_at', self::Instance()->fillable) && $columns[] = 'deleted_at';
            in_array('deleted_by', self::Instance()->fillable) && $columns[] = 'deleted_by';
        }
        return $columns;
    }


    public function created_user()
    {
        if (in_array('created_by', self::Instance()->fillable)) {
            return $this->belongsTo(SyncUser::class, 'created_by', 'user_id')
                ->select([
                    'user_id',
                    'name',
                    'email'
                ]);
        }
        return null;
    }


    public function updated_user()
    {
        if (in_array('updated_by', self::Instance()->fillable)) {
            return $this->belongsTo(SyncUser::class, 'updated_by', 'user_id')
                ->select([
                    'user_id',
                    'name',
                    'email'
                ]);
        }
        return null;
    }

    public function deleted_user()
    {
        if (in_array('deleted_by', self::Instance()->fillable)) {
            return $this->belongsTo(SyncUser::class, 'deleted_by', 'user_id')
                ->select([
                    'user_id',
                    'name',
                    'email'
                ]);
        }
        return null;
    }


    public function createUuid($prefix = "")
    {
        $chars = md5(uniqid());
        $uuid  = substr($chars, 0, 8) . '-'
            . substr($chars, 8, 4) . '-'
            . substr($chars, 12, 4) . '-'
            . substr($chars, 16, 4) . '-'
            . substr($chars, 20, 12);
        return $prefix . $uuid;
    }

    public function getRules()
    {
        if (is_array(self::$rules)) {
            return self::$rules;
        }
        $table = self::Instance()->getTable();

        $sql = 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = :db AND TABLE_NAME = :table';
        if (!DB::selectOne($sql, ['db' => env('DB_DATABASE'), 'table' => $table])) {
            return [];
        }

        $rules   = [];
        $columns = Db::select("SHOW FULL COLUMNS FROM `$table`");
        foreach ($columns as $column) {
            $rule = [
                'type'     => 'string',
                'range'    => '',
                'required' => '',
            ];

            $typeArr = explode('(', $column->Type);

            $getRange = function ($typeArr1) {
                $length = explode(')', $typeArr1)[0];
                //between:1,20
                return "max:$length";
            };
            switch ($typeArr[0]) {
                case 'int':
                case 'tinyint':
                    $rule['type'] = 'integer';
                    break;
                case 'decimal':
                    $rule['type'] = 'numeric';
                    break;
                case 'datetime':
                    $rule['type'] = 'date';
                    break;
                case 'enum':
                    $enums         = str_replace("'", '', explode(')', $typeArr[1])[0]);
                    $rule['range'] = "in:$enums";
                    break;
                case 'bit':
                    $rule['type'] = 'boolean';
                    break;
                case 'char':
                case 'varchar':
                    $rule['type']     = 'string';
                    $rule['range']    = $getRange($typeArr[1]);
                    $rule['required'] = 'nullable';
                    break;

                default:
            }
            ($column->Null == 'NO' && is_null($column->Default)) && $rule['required'] = "required";
            ($column->Key == 'UNI') && $rule['range'] = "unique:{$this->table},{$column->Field}";

            if (in_array($column->Field, ['created_by', 'updated_by', 'deleted_by'])) {
                $rule['range'] = 'uuid';
            }

            $rules[$column->Field] = $rule;
        }
        self::$rules = $rules;
        return self::$rules;
    }

    public function composeRule($type = 'string', $range = '', $required = '')
    {
        $rule = [];
        $type && $rule['type'] = $type;
        $range && $rule['range'] = $range;
        $required && $rule['required'] = $required;
        return $rule;
    }

    public function composeRules($rules = [])
    {
        $return = [];
        if ($rules) {
            foreach ($rules as $field => $rule) {
                $return[$field] = self::Instance()->composeRule(
                    $rule['type'] ?? '',
                    $rule['range'] ?? '',
                    $rule['required'] ?? ''
                );
            }
        }
        return $return;
    }

    /**
     * batch update
     * @param array $multipleData - 2 d array: [['seller_code' => 'xx', 'name' => 'xx'],[...]...]
     * @param null $tableName - table name: seller
     * @param null $field - field conditions: seller_code
     * @return bool|int
     */
    public function updateBatch($multipleData = [], $tableName = null, $field = null)
    {
        try {
            if (empty($multipleData)) return false;
            if (empty($tableName)) return false; // table name

            $firstRow = current($multipleData);

            $updateColumn = array_keys($firstRow);

            // the default update is conditional on seller_code, or conditional on the first field if there is no seller_code
            $referenceColumn = isset($firstRow[$field]) ? $field : current($updateColumn);
            foreach ($updateColumn as $k => $v) {
                if ($v == $referenceColumn) unset($updateColumn[$k]);
            }

            // concatenation sql statement
            $updateSql = "UPDATE " . $tableName . " SET ";
            $sets      = [];
            $bindings  = [];
            foreach ($updateColumn as $uColumn) {
                $setSql = "`" . $uColumn . "` = CASE ";
                foreach ($multipleData as $data) {
                    $setSql     .= "WHEN `" . $referenceColumn . "` = ? THEN ? ";
                    $bindings[] = $data[$referenceColumn];
                    $bindings[] = $data[$uColumn];
                }
                $setSql .= "ELSE `" . $uColumn . "` END ";
                $sets[] = $setSql;
            }
            $updateSql .= implode(', ', $sets);
            $whereIn   = collect($multipleData)->pluck($referenceColumn)->values()->all();
            $bindings  = array_merge($bindings, $whereIn);
            $whereIn   = rtrim(str_repeat('?,', count($whereIn)), ',');
            $updateSql = rtrim($updateSql, ", ") . " WHERE `" . $referenceColumn . "` IN (" . $whereIn . ")";

            // the pre-processed sql statement and the corresponding binding data are passed in
            return DB::update($updateSql, $bindings);

        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * batch save - insert/update
     * @param array $multipleData - 2 d array: [['seller_code' => 'xx', 'name' => 'xx'],[...]...]
     * @param null $tableName - table name
     * @return bool
     */
    public function batchReplaceInto($multipleData = [], $tableName = null)
    {
        try {
            if (empty($multipleData)) return false;
            if (empty($tableName)) return false; // table name

            $firstRow = current($multipleData);

            $updateColumn = array_keys($firstRow);
            $fields       = implode(',', $updateColumn);

            // concatenation sql statement
            $sql = "REPLACE INTO {$tableName} ({$fields}) values ";
            foreach ($multipleData as $k => $val) {
                $comma = '';
                if (($k + 1) < count($multipleData)) $comma = ',';

                $i   = 0;
                $sql .= '(';
                foreach ($val as $j => $v) {
                    $com = '';
                    if (($i + 1) < count($val)) $com = ',';
                    if ($v != '') {
                        $sql .= "'" . $v . "'" . $com;
                    } else {
                        $sql .= "null" . $com;
                    }

                    $i++;
                }
                $sql .= ')' . $comma;
            }

            return DB::statement($sql);

        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * 构造单条数据
     * @param $data
     */
    protected static function makeEntity($data, $allowDeletedAt = false)
    {
        $model = new static();
        if (isset($data['id']) && $data['id'] !== '') {
            $entity = static::query()->findOrFail($data['id']);
            if ($entity) {
                $model = $entity;
            }
        }
        $fillable = $model->getFillable();
        if (!$allowDeletedAt) {
            unset($fillable['deleted_at'], $fillable['deleted_by']);
        }
        $data = Arr::only($data, $fillable);
        $model->fill($data);
        return $model;
    }

    /**
     * 保存单条数据,返回执行结果
     * @param $data
     * @return bool|object
     */
    public static function saveEntity($data, $allowDeletedAt = false)
    {
        $model = static::makeEntity($data, $allowDeletedAt);
        $res   = $model->save();
        if ($res) {
            return $model;
        } else {
            return false;
        }
    }

    /**
     * @param $id
     * @param string $deletedBy
     * @return bool
     */
    public static function deleteEntity($id, $deletedBy = '')
    {
        $entity             = static::query()->findOrFail($id);
        $entity->deleted_by = $deletedBy;
        $entity->deleted_at = time();
        return $entity->save();
    }

    /*
        public function beforeCreate()
        {
        }

        public function beforeUpdate()
        {
        }

        public function beforeSave()
        {
        }

        public function afterUpdate()
        {
        }

        public function afterSave()
        {
        }
        */
}
